package cn.jasonone.tio.tcp.handler;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.jasonone.tio.tcp.commons.Command;
import cn.jasonone.tio.tcp.commons.CommandFactory;
import cn.jasonone.tio.tcp.model.Data;
import cn.jasonone.tio.tcp.model.Header;
import cn.jasonone.tio.tcp.model.Heartbeat;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.tio.client.intf.ClientAioHandler;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.TioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.server.intf.ServerAioHandler;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据处理器
 *
 * @author xujie
 * @version 1.0
 * @date 2022/1/18 10:13
 */
@Slf4j
@RequiredArgsConstructor
public class DataAioHandler implements ServerAioHandler, ClientAioHandler {
    private static final Marker MARKER_COMMAND = MarkerFactory.getMarker("Command");
    private static final Marker MARKER_HEARTBEAT = MarkerFactory.getMarker("Heartbeat");
    private static final Marker MARKER_SERIALIZATION = MarkerFactory.getMarker("Serialization");
    private static final Marker MARKER_ENCRYPTION = MarkerFactory.getMarker("Encryption");
    /**
     * 心跳处理器
     */
    @Autowired(required = false)
    private HeartbeatHandler heartbeatHandler;
    @Autowired(required = false)
    private SignatureHandler signatureHandler;
    /**
     * 虚拟化处理器
     */
    private final SerializationHandler serializationHandler;
    /**
     * 指令工厂
     */
    private final CommandFactory commandFactory;
    /**
     * 加解密处理器
     */
    private final EncryptionHandler encryptionHandler;

    @Override
    public Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws TioDecodeException {
        //提醒：buffer的开始位置并不一定是0，应用需要从buffer.position()开始读取数据
        //收到的数据组不了业务包，则返回null以告诉框架数据不够
        if (readableLength < Header.HEADER_LENGTH) {
            return null;
        }
        Data data = new Data();
        // 解析消息头
        parseHeader(buffer, data);
        //读取消息体的长度
        //数据不正确，则抛出AioDecodeException异常
        if (data.getBodyLength() < 0) {
            throw new TioDecodeException("bodyLength [" + data.getBodyLength() + "] is not right, remote:" + channelContext.getClientNode());
        }
        //计算本次需要的数据长度
        int neededLength = Header.HEADER_LENGTH + data.getBodyLength();
        //收到的数据是否足够组包
        int isDataEnough = readableLength - neededLength;
        // 不够消息体长度(剩下的buffe组不了消息体)
        if (isDataEnough < 0) {
            return null;
        } else //组包成功
        {
            if (data.getBodyLength() > 0) {
                byte[] dst = new byte[data.getBodyLength()];
                buffer.get(dst);
                if (data.isEncryption()) {
                    // 解密消息体
                    try {
                        dst = encryptionHandler.decryption(dst);
                    } catch (Exception e) {
                        log.error(MARKER_ENCRYPTION, "消息体解密失败,消息头信息:{}", data.getHeader(), e);
                    }
                }
                // 反序列化消息体
                try {
                    Map<String, Object> context = new HashMap<>();
                    Header header = ObjectUtil.clone(data.getHeader());
                    if (commandFactory.hasCommandHandler(data.getCommand())) {
                        CommandHandler commandHandler = commandFactory.getCommandHandler(data.getCommand());
                        // 执行反序列化前置操作
                        dst = commandHandler.deserializeBefore(context, header, dst);
                    }
                    Object deserialization = serializationHandler.deserialize(header, context, dst, data.getCharset());
                    data.setBody(deserialization);
                } catch (Exception e) {
                    log.error(MARKER_SERIALIZATION, "消息体反序列化失败,消息头信息:{}", data.getHeader(), e);
                }
            }
            if (signatureHandler != null) {
                Assert.isTrue(signatureHandler.checkSignature(data), "验签失败:{}", data);
            }
            return data;
        }
    }


    @Override
    public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
        Data data = (Data) packet;
        if (signatureHandler != null) {
            // 进行数据包签名
            signatureHandler.signature(data);
        }
        Object body = data.getBody();
        byte[] bytes = null;
        if (body != null) {
            // 序列化消息体
            try {
                Map<String, Object> context = new HashMap<>();
                Header header = ObjectUtil.clone(data.getHeader());
                // 执行序列化前置处理
                if (commandFactory.hasCommandHandler(data.getCommand())) {
                    body = commandFactory.getCommandHandler(data.getCommand()).serializeBefore(context, header, body);
                }
                bytes = serializationHandler.serialize(header, context, body, data.getCharset());
                try {
                    if (data.isEncryption()) {
                        // 加密消息体
                        bytes = encryptionHandler.encryption(bytes);
                    }
                    data.setBodyLength(bytes.length);
                } catch (Exception e) {
                    log.error(MARKER_ENCRYPTION, "消息体加密失败:{}", body, e);
                }
            } catch (Exception e) {
                log.error(MARKER_SERIALIZATION, "消息体序列化失败:{}", body, e);
            }
        }
        //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
        int total = Header.HEADER_LENGTH + data.getBodyLength();
        //创建一个新的bytebuffer
        ByteBuffer buffer = ByteBuffer.allocate(total);
        //设置字节序
        buffer.order(tioConfig.getByteOrder());
        //写入消息头----消息头的内容就是消息体的长度
        writeHeader(buffer, data);
        //写入消息体
        if (body != null) {
            buffer.put(bytes);
        }
        return buffer;
    }

    private void writeHeader(ByteBuffer buffer, Data data) {
        // 写入字符集编码
        buffer.put(data.getHeader().getCharsetCode());
        // 写入业务ID
        buffer.put(data.getHeader().getBidToArray());
        // 写入消息体加密状态
        buffer.put((byte) (data.isEncryption() ? 1 : 0));
        // 写入指令类型
        buffer.put(data.getCommand().getType());
        // 写入指令分组编码
        buffer.putInt(data.getCommand().getGroup());
        // 写入指令编码
        buffer.putInt(data.getCommand().getCode());
        // 写入消息签名
        buffer.put(data.getHeader().getSignatureToArray());
        // 写入消息体长度
        buffer.putInt(data.getBodyLength());
    }

    private void parseHeader(ByteBuffer buffer, Data data) {
        byte[] bytes = new byte[32];
        // 解析字符集编码
        data.setCharsetCode(buffer.get());
        // 解析业务ID
        buffer.get(bytes, 0, 32);
        data.setBId(new String(bytes, data.getCharset()).trim());
        Arrays.fill(bytes, (byte) 0);
        // 解析消息体加密状态
        data.setEncryption(buffer.get() == 1);

        // 解析命令
        {
            byte type = buffer.get();
            int group = buffer.getInt();
            int code = buffer.getInt();
            try {
                Command command = commandFactory.getCommand(type, group, code);
                data.setCommand(command);
            } catch (Exception e) {
                log.error(MARKER_COMMAND, "指令解析失败,指令信息:{type:{},group:{},code:{}}", new Object[]{type, group, code}, e);
            }
        }
        // 解析消息签名
        buffer.get(bytes, 0, 32);
        data.getHeader().setSignature(new String(bytes, data.getCharset()));
        Arrays.fill(bytes, (byte) 0);
        // 写入消息体长度
        data.setBodyLength(buffer.getInt());
    }

    @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        // 处理命令
        Data data = (Data) packet;
        try {
            commandFactory.handler(data, channelContext);
        } catch (Exception e) {
            log.error(MARKER_COMMAND, "指令处理失败:{}", data.getCommand(), e);
        }
    }

    @Override
    public Packet heartbeatPacket(ChannelContext ctx) {
        if (heartbeatHandler != null) {
            try {
                Heartbeat heartbeat = new Heartbeat();
                heartbeat.setBId(ctx.getBsId());
                heartbeat.setEncryption(false);
                heartbeat.setBody(heartbeatHandler.createHeartbeat(ctx));
                return heartbeat;
            } catch (Exception e) {
                log.error(MARKER_HEARTBEAT, "心跳包创建失败", e);
            }
        }
        return null;
    }
}
